using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

namespace Decav.Security
{
    /// <summary>
    /// Holds the password of a in a hashed format.
    /// </summary>
    [Serializable]
    public struct HashedPassword
    {
        private HashedPassword(Byte[] hash)
        {
            _hash = hash;
        }

        private Byte[] _hash;

        /// <summary>
        /// The empty value of a password.
        /// </summary>
        public static HashedPassword Empty = new HashedPassword();

        /// <summary>
        /// Hashes the password in its raw value into a hash that can be sent accross a wire or
        /// saved to the database.
        /// </summary>
        /// <param name="rawPassword">The users raw password.</param>
        /// <returns>The password struct, containing the hashed value.</returns>
        public static HashedPassword HashPassword(string rawPassword)
        {
            if (rawPassword == null)
                throw new ArgumentNullException("rawPassword");

            MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();
            return new HashedPassword( provider.ComputeHash(ASCIIEncoding.Unicode.GetBytes(rawPassword)) );
        }

        /// <summary>
        /// Creates a <see cref="HashedPassword"/> from an already hashed value.
        /// </summary>
        /// <param name="asciiHash">The hash value in ASCII encoding.</param>
        /// <returns>The password struct containing the password.</returns>
        public static HashedPassword FromHash(string asciiHash)
        {
            if (asciiHash == null)
                throw new ArgumentNullException("asciiHash");

            return new HashedPassword(ASCIIEncoding.ASCII.GetBytes(asciiHash));
        }

        /// <summary>
        /// Creates a <see cref="HashedPassword"/> from an already hashed value.
        /// </summary>
        /// <param name="hash">The hash value's bytes.</param>
        /// <returns>The password struct containing the password.</returns>
        public static HashedPassword FromHash(Byte[] hash)
        {
            if (hash == null)
                throw new ArgumentNullException("hash");

            return new HashedPassword(hash);
        }

        /// <summary>
        /// Returns the hash's bytes.
        /// </summary>
        /// <returns>The bytes of the hash.</returns>
        public byte[] ToByteArray()
        {
            Byte[] hash = new Byte[_hash.Length];
            Array.Copy(_hash, hash, hash.Length);
            return hash;
        }

        /// <summary>
        /// An ASCII string representation of the hash.
        /// </summary>
        /// <returns>The hash of the password as an ASCII string.</returns>
        public override string ToString()
        {
            return ASCIIEncoding.ASCII.GetString(_hash);
        }

        public override int GetHashCode()
        {
            if (_hash == null)
                return (new Byte[] { }).GetHashCode();

            return _hash.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (obj.GetType() != typeof(HashedPassword))
                return false;

            HashedPassword p = ((HashedPassword)obj);

            if (this._hash == null && p._hash == null)
                return true;
            else if (this._hash == null)
                return false;

            return this._hash.Equals(p._hash);
        }

        public static bool operator ==(HashedPassword left, HashedPassword right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(HashedPassword left, HashedPassword right)
        {
            return !left.Equals(right);
        }
    }
}
